/*
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
*/ //in the "Packet.h"
#include "Packet.h"
/*
//#include <limits.h> already ，
//it's has already #define PATH_MAX , so:
//#undef PATH_MAX
//#define PATH_MAX 4096
//该步已move至 .h file 中。
*/
/* TODO (dmsj#1#): 兼容性[不同系统间的地址风格分隔符
                   不同]，需处理。 */

#if _WIN32 || _WIN64

/*
动态路径构建函数*/
wchar_t* BuildFullPath(const wchar_t* base, const wchar_t* rel)
{
	if (base == NULL || rel == NULL)
		return NULL;
	//  使用 _snwprintf 计算拼接后的字符串长度，+1 是为了包括终止符 '\0'
	int len = _snwprintf(NULL, 0, L"%s\\%s", base, rel) + 1; 
	if (len <= 1)
		return NULL;

	wchar_t* buf = (wchar_t*)malloc(len * sizeof(wchar_t));
	if (!buf)
		return NULL;
	//将 base 和 rel 拼接成一个新的字符串存储在 buf 中 , _TRUNCATE 表示如果字符串长度超过 buf 的容量，则截断字符串
	if ( -1 == _snwprintf_s(buf, len, _TRUNCATE, L"%s\\%s", base, rel) ) 
	{
		free(buf);
		return NULL;
	}

	return buf;
}

/*
多级目录创建函数_pro*/
void CreateDirectoryRecursive(const wchar_t* path)
{
	if (!path || wcslen(path) == 0)
		return;

	wchar_t* current = _wcsdup(path); // == strdup()
	for (wchar_t* p = current; *p; ++p)
		if (*p == L'\\' || *p == L'/')
		{
			*p = L'\0';
			CreateDirectoryW(current, NULL);
			*p = L'\\';
		}

	CreateDirectoryW(current, NULL);
	free(current);
}
/*
路径处理函数*/
void PathRemoveFileRecursive(wchar_t* full_path)
{
	wchar_t* last_slash = wcsrchr(full_path, L'\\'); //  查找最后一个反斜杠的位置，即文件名的起始位置

	if (last_slash)
		*last_slash = L'\0';
	else
		wcscpy(full_path, L"."); //  如果没有找到反斜杠，说明路径中只有文件名，将其替换为当前目录"."
}

/*
递归遍历目录
概述：该函数用于遍历指定目录及其子目录，并将目录和文件信息写入到一个打包文件中。
参数：
base_path：宽字符字符串，表示要遍历的根目录路径。
rel_path：宽字符字符串，表示相对于根目录的当前目录路径。
pack_file：指向已打开的文件的指针，用于写入打包数据。
flag：字符，表示特定的标志，用于写入文件或目录条目时使用。
@ret：无返回值（void 类型）*/
void traverse_dir(const wchar_t* base_path, const wchar_t* rel_path, FILE* pack_file, const char flag)
{
	// 构建完整的搜索路径，如果相对路径为空则使用根路径
	wchar_t* search_path = BuildFullPath(base_path, (wcslen(rel_path) ? rel_path : L""));
	if (!search_path)
		return; // 如果构建失败，直接返回

	// 构建搜索模式，即在搜索路径下查找所有文件和目录
	wchar_t* search_pattern = BuildFullPath(search_path, L"*");
	free(search_path);//及时处理

	WIN32_FIND_DATAW find_data;
	HANDLE hFind = FindFirstFileW(search_pattern, &find_data);
	free(search_pattern);//及时处理

	if (hFind == INVALID_HANDLE_VALUE)
		return;

	do
	{
		if (0 == wcscmp(find_data.cFileName, L".") ||
		        wcscmp(find_data.cFileName, L"..") == 0)
			continue;

		wchar_t* new_rel_path = NULL;

		/*使用 ?:; 优化替代
				if (wcslen(rel_path) > 0)
					new_rel_path = BuildFullPath(rel_path, find_data.cFileName);
				else //<=0
					new_rel_path = _wcsdup(find_data.cFileName);
		*/
		new_rel_path = (wcslen(rel_path) > 0) ? //  判断相对路径是否为空，如果不为空则构建完整路径，否则复制文件名
		               BuildFullPath(rel_path, find_data.cFileName) : _wcsdup(find_data.cFileName);


		if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //  检查当前文件属性是否为目录
		{
			write_dir_entry(pack_file, base_path,new_rel_path);
			traverse_dir(base_path, new_rel_path, pack_file,flag);
		}
		else
			write_file_entry(pack_file, base_path, new_rel_path,flag);


		free(new_rel_path); //  释放新相对路径的内存
	}
	while (FindNextFileW(hFind, &find_data)); //  循环查找下一个文件或目录，直到没有更多文件或目录

	FindClose(hFind);
}
/*
写入目录条目*/
void write_dir_entry(FILE* pack_file, const wchar_t* base_path, const wchar_t* rel_path)
{
	//文件类别_判入。
	const uint8_t type = 0;
	fwrite(&type, sizeof(type), 1, pack_file);
	//路径_判入。
	char* utf8_path = WideToUTF8(rel_path);
	if (!utf8_path)
		return;

	uint32_t path_len = (uint32_t)strlen(utf8_path);
	fwrite(&path_len, sizeof(path_len), 1, pack_file);
	fwrite(utf8_path, 1, path_len, pack_file);

	free(utf8_path);
	//路径+路径长度处理_end.

	// 构建目录的绝对路径
	wchar_t* full_dir_path = BuildFullPath(base_path, rel_path);
	if (!full_dir_path)
		return;
	// 获取目录属性
	WIN32_FILE_ATTRIBUTE_DATA attr;
	if (!GetFileAttributesExW(full_dir_path, GetFileExInfoStandard, &attr))
	{
		fprintf(stderr, "无法获取目录属性: %ls\n", full_dir_path),
		        free(full_dir_path);
		return;
	}
	// 写入属性
	fwrite(&attr.dwFileAttributes, sizeof(DWORD), 1, pack_file);
	fwrite(&attr.ftCreationTime, sizeof(FILETIME), 1, pack_file);
	fwrite(&attr.ftLastAccessTime, sizeof(FILETIME), 1, pack_file);
	fwrite(&attr.ftLastWriteTime, sizeof(FILETIME), 1, pack_file);
	//end
	free(full_dir_path);

}
/*
写入文件条目==[压缩+打包】*/
void write_file_entry(FILE* pack_file, const wchar_t* base_path, const wchar_t* rel_path, const char flag)
{
	//文件类别_判入。
	const uint8_t type = 1;
	fwrite(&type, sizeof(type), 1, pack_file);
	//路径_判入。
	char* utf8_path = WideToUTF8(rel_path);
	if (!utf8_path)
		return;

	uint32_t path_len = (uint32_t)strlen(utf8_path);
	fwrite(&path_len, sizeof(path_len), 1, pack_file);
	fwrite(utf8_path, 1, path_len, pack_file);
	free(utf8_path);
	//路径+路径长度处理_end.

	wchar_t* full_path = BuildFullPath(base_path, rel_path);
	if (!full_path)
		return;
	/* 原版时间戳_get:过于单一，已舍弃
	// 获取属性1:file_open
	HANDLE hFile = CreateFileW(full_path, GENERIC_READ, FILE_SHARE_READ, NULL,
	                           OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		fprintf(stderr, "无法打开文件以获取时间戳: %ls\n", full_path),
		        free(full_path);
		return;
	}
	//获取时间戳2:get
	FILETIME creation, access, write;
	if (!GetFileTime(hFile, &creation, &access, &write))
	{
		fprintf(stderr, "获取时间戳失败: %ls\n", full_path),
		        CloseHandle(hFile),free(full_path);
		return;
	}
	CloseHandle(hFile);
	// 写入三个时间戳::时间戳_判入
	fwrite(&creation, sizeof(FILETIME), 1, pack_file);
	fwrite(&access, sizeof(FILETIME), 1, pack_file);
	fwrite(&write, sizeof(FILETIME), 1, pack_file);
	*/
	// 获取file属性
	WIN32_FILE_ATTRIBUTE_DATA attr;
	if (!GetFileAttributesExW(full_path, GetFileExInfoStandard, &attr))
	{
		fprintf(stderr, "无法获取文件属性: %ls\n", full_path),
		        free(full_path);
		return;
	}
	// 写入属性
	fwrite(&attr.dwFileAttributes, sizeof(DWORD), 1, pack_file);
	fwrite(&attr.ftCreationTime, sizeof(FILETIME), 1, pack_file);
	fwrite(&attr.ftLastAccessTime, sizeof(FILETIME), 1, pack_file);
	fwrite(&attr.ftLastWriteTime, sizeof(FILETIME), 1, pack_file);

	//元数据_判入
	FILE* src_file = _wfopen(full_path, L"rb");
	free(full_path);//作用结束，及时处理，以防忘记。
	if (!src_file)
	{
		fprintf(stderr,"\n src_file open failed\n");
		return;
	}

	//直接打包_file_size
	_fseeki64(src_file, 0, SEEK_END);
	uint64_t file_size = _ftelli64(src_file);
	_fseeki64(src_file, 0, SEEK_SET);
	fwrite(&file_size, sizeof(file_size), 1, pack_file);

	/*/kernel_1:
	//压缩+打包/
	*/
	switch(flag)
	{
		case 'p': //只packet
			//分块_write
			char buffer[BUFFER_SIZE];
			size_t bytes_read;
			while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, src_file)) > 0)
				fwrite(buffer, 1, bytes_read, pack_file); //直接打包。

			break;

		case 'P'://packet+文件压缩
			//压缩步骤。
			lzw_compress(src_file,pack_file);
			break;

		default:
			fprintf(stderr,"invaild order.\n");
	}

	fclose(src_file);
}

/*
打包函数
概述：该函数用于将指定目录及其子目录中的文件packet[或者packet+compress]到一个输出文件中。
参数：
root_dir：宽字符字符串，表示要打包的根目录路径。
output_file：宽字符字符串，表示输出的打包文件路径。
flag：字符，表示打包时的特定标志。
@ret：无返回值*/
void pack_directory(const wchar_t* root_dir, const wchar_t* output_file, const char flag)
{
	FILE* pack_file = _wfopen(output_file, L"wb");
	if (!pack_file)
	{
		wprintf(L"error: can't create the file.pack: %s\n", output_file);
		return;
	}

	fwrite(FILE_HEADER, 1, FILE_HEADER_LEN, pack_file);
	traverse_dir(root_dir, L"", pack_file,flag); //kernel
	fclose(pack_file);

	wprintf(L"Pack_fin: %s\n", output_file);
}
/*
解包函数==[解压+拆包]
概述：该函数用于unpacket[或者unpacket+decompress]一个文件到指定的目录。
参数：
pack_file：待解压的文件路径。
output_dir：解压文件的目标目录。
flag：字符，表示解包时的特定标志。
@ret：无返回值*/
void unpack_directory(const wchar_t* pack_file, const wchar_t* output_dir,const char flag)
{
	FILE* fp = _wfopen(pack_file, L"rb");
	if (!fp)
	{
		wprintf(L"error: can't open the file.pack: %s\n", pack_file);
		return;
	}

	char header[FILE_HEADER_LEN];
	if (fread(header, 1, FILE_HEADER_LEN, fp) != FILE_HEADER_LEN ||
	        memcmp(header, FILE_HEADER, FILE_HEADER_LEN) != 0) //  检查文件头是否正确
	{
		fclose(fp);
		wprintf(L"invalid file_format::HEADER_ERROR \n");
		return;
	}

	CreateDirectoryRecursive(output_dir);
	while (!feof(fp))
	{
		uint8_t type; //  读取文件类型
		if (fread(&type, sizeof(type), 1, fp) != 1)
			break;

		uint32_t path_len; //  读取文件路径长度
		if (fread(&path_len, sizeof(path_len), 1, fp) != 1)
			goto flag_error;

		char* utf8_path = (char*)malloc(path_len + 1);
		if (!utf8_path || fread(utf8_path, 1, path_len, fp) != path_len) //  检查内存分配是否成功以及是否成功读取路径数据
		{
			free(utf8_path);
			goto flag_error;
			//break;
		}
		utf8_path[path_len] = '\0';

		wchar_t* rel_path = UTF8ToWide(utf8_path, path_len);
		free(utf8_path);
		if (!rel_path)
			continue;

		wchar_t* full_path = BuildFullPath(output_dir, rel_path); //  构建完整路径

		if (type == 0)//dir //  根据类型处理目录或文件
		{
			CreateDirectoryRecursive(full_path); //  递归创建目录
//			CreateDirectoryW(full_path, NULL);
			// 读取目录属性并设置
			DWORD dir_attrs;
			FILETIME creation, access, write;
			if (fread(&dir_attrs, sizeof(DWORD), 1, fp) != 1   ||
			        fread(&creation, sizeof(FILETIME), 1, fp) != 1 ||
			        fread(&access, sizeof(FILETIME), 1, fp) != 1   ||
			        fread(&write, sizeof(FILETIME), 1, fp) != 1		)
			{
				free(rel_path), free(full_path);
				goto flag_error;
			}
			// 设置时间戳
			HANDLE hDir = CreateFileW(full_path, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
			                          OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
			if (hDir != INVALID_HANDLE_VALUE)
				SetFileTime(hDir, &creation, &access, &write), CloseHandle(hDir);
			else
				fprintf(stderr, "设置目录属性失败: %ls (错误码: %lu)\n",
				        full_path, GetLastError());

			// 设置目录属性（包括隐藏属性）
			if (!SetFileAttributesW(full_path, dir_attrs))
				fprintf(stderr, "设置目录属性失败: %ls (错误码: %lu)\n",
				        full_path, GetLastError());

		}
		else if (type == 1)//file
		{
			wchar_t* parent_dir = _wcsdup(full_path);

			PathRemoveFileRecursive(parent_dir);
			CreateDirectoryRecursive(parent_dir);
//			CreateDirectoryW(parent_dir, NULL);
			free(parent_dir);

			//读取file属性和时间戳
			DWORD file_attrs;
			FILETIME creation, access, write;
			if (
			    fread(&file_attrs, sizeof(DWORD), 1, fp) != 1  ||
			    fread(&creation, sizeof(FILETIME), 1, fp) != 1 ||
			    fread(&access, sizeof(FILETIME), 1, fp) != 1   ||
			    fread(&write, sizeof(FILETIME), 1, fp) != 1		)
			{
				free(rel_path), free(full_path);
				goto flag_error;
			}

			uint64_t file_size;
			if (fread(&file_size, sizeof(file_size), 1, fp) != 1)
			{
				free(rel_path), free(full_path);
				goto flag_error;
			}

			FILE* dest_file = _wfopen(full_path, L"wb");
			if (!dest_file)
			{
				fprintf(stderr,"\n dest_file open failed\n");

				free(rel_path), free(full_path);
				goto flag_error;
			}

			switch(flag)
			{
				/*
				直接打包时：*/
				case 'd': //只de_packet
				{
					uint64_t remaining = file_size;
					char buffer[BUFFER_SIZE];

					while (remaining > 0)
					{
//						size_t read_size = remaining > BUFFER_SIZE ? BUFFER_SIZE : (size_t)remaining;
						size_t read_size = auto_nmemb((size_t)remaining, BUFFER_SIZE);
						size_t bytes_read = fread(buffer, 1, read_size, fp);

						if (bytes_read == 0)
							break;

						fwrite(buffer, 1, bytes_read, dest_file);
						remaining -= bytes_read;
					}
				}
				break;

				case 'D'://de_packet+文件解压（de_compress）
					//解压步骤。
					lzw_decompress(fp,dest_file);
					break;

				default:
					fprintf(stderr,"invaild order.\n");
			}
			fclose(dest_file);

			// 创建文件并写入内容后设置时间戳
			HANDLE hFile = CreateFileW(full_path, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, NULL,
			                           OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
			if (hFile != INVALID_HANDLE_VALUE)
				SetFileTime(hFile, &creation, &access, &write), CloseHandle(hFile);
			else
				fprintf(stderr, "设置文件属性失败: %ls (错误码: %lu)\n",
				        full_path, GetLastError());
			// 设置文件属性
			if (!SetFileAttributesW(full_path, file_attrs))
				fprintf(stderr, "设置文件属性失败: %ls (错误码: %lu)\n",
				        full_path, GetLastError());

		}
		free(rel_path), free(full_path);
	}

	fclose(fp);
	wprintf(L"unPack_fin: %s\n", output_dir);
	return ;

	int flag_error;
flag_error :
	fclose(fp);
	perror("error: ");
	fprintf(stderr,"\n ERROR: unPack_failed.\n");
	return ;
}

#else //LINUX
// create multi_dir
void re_mkdir(const char *path)
{
	char tmp[PATH_MAX]; //[0..PATH_MAX-1]
	char *p = NULL;
	size_t len;  //机字长

	snprintf(tmp, sizeof(tmp), "%s", path); // format :string.
	len = strlen(tmp);
	// 意外处理step case: “.././ ”
	if (tmp[len - 1] == '/')
		tmp[len - 1] = '\0';
	//利用‘\0’ to infer EOF
	for (p = tmp + 1; *p ; p++)
	{
		if (*p == '/')
		{
			*p = '\0';
			/*string 的识别：
			当碰到字符‘\0’时，自动停止【或者说算一个字符】，
			利用这个特性，可以实现在一次for循环内，快速生成多级目录*/
			mkdir(tmp, 0755);

			*p = '/';
		}
	}
//	mkdir(tmp, 0755); Windows不兼容
	mkdir(tmp,0755);
}
// 递归打包目录
void su_pack(const char *base_dir, const char *current_dir, FILE *packed_file)
{
	char full_path[PATH_MAX];
	snprintf(full_path, sizeof(full_path), "%s/%s", base_dir, current_dir);
//	snprintf(full_path, sizeof(full_path), "%s\\%s", base_dir, current_dir);

	DIR *dir = opendir(full_path);

	if (!dir)
		return;

	struct dirent *entry;
	while (( entry = readdir(dir) )) // 0 or 1 :: dir/else
	{
		if ( 0 == strcmp(entry->d_name, ".") || strcmp(entry->d_name, "..") == 0)
			continue;

		char relative_path[PATH_MAX];
		if (strlen(current_dir) == 0)
			snprintf(relative_path, sizeof(relative_path), "%s", entry->d_name);
		else
			snprintf(relative_path, sizeof(relative_path), "%s/%s", current_dir, entry->d_name);
//			snprintf(relative_path, sizeof(relative_path), "%s\\%s", current_dir, entry->d_name);

		char entry_path[PATH_MAX];
		snprintf(entry_path, sizeof(entry_path), "%s/%s", base_dir, relative_path);
//		snprintf(entry_path, sizeof(entry_path), "%s\\%s", base_dir, relative_path);

		struct stat st;
		//stat_function :get文件或目录状态info[包括type\size\time..]。
		if (stat(entry_path, &st) == -1)
		{
			perror("stat_ERROR ");
			continue;
		}

		// 写入路径信息
		uint16_t path_len = strlen(relative_path);
		fwrite(&path_len, sizeof(uint16_t), 1, packed_file);
		fwrite(relative_path, 1, path_len, packed_file);

//		#define	S_ISDIR(m)	(((m) & S_IFMT) == S_IFDIR)
		if (S_ISDIR(st.st_mode)) //若current is directory.
		{
			uint8_t type = 0; //dir
			fwrite(&type, sizeof(uint8_t), 1, packed_file);
			uint64_t size = 0; // no size
			fwrite(&size, sizeof(uint64_t), 1, packed_file);
			//递归
			su_pack(base_dir, relative_path, packed_file);
		}
		else // any_file - exclude_directory
		{
			uint8_t type = 1;
			fwrite(&type, sizeof(uint8_t), 1, packed_file);
			uint64_t size = st.st_size;
			fwrite(&size, sizeof(uint64_t), 1, packed_file);

			FILE *file = fopen(entry_path, "rb"); //read_only binary
			if (!file)
			{
				perror("fopen_ERROR "); //stderr
				continue; // goto the next time of while directly.
				// replace using the 'goto' sentence.
			}

//			char buffer[4096];
			char buffer[PATH_MAX]; //缓冲带,[c不直接提供缓冲区，只能自己造轮子，这里简略用数组replace]。
			size_t bytes_read; //机字长
			/*fread function:
				fread function:
				@ret : size_t, refer to 'sizeof'.
				is nmemb-- 这是元素的个数，每个元素的大小为 size 字节。
			*/			//这里不用EOF,是考虑到减少（一次）循环次数,即为0时,实际上也可fwrite，但无意义。【up eff.】
			while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) // > eof + 1
				fwrite(buffer, 1, bytes_read, packed_file);

			fclose(file);
		}
	}

	closedir(dir);
}
// 解包函数
void un_pack(FILE *packed_file, const char *output_dir)
{
	uint16_t path_len;
	while (1 == fread(&path_len, sizeof(uint16_t), 1, packed_file) )//既定结构
	{
		//get path
		char path[PATH_MAX];
		fread(path, 1, path_len, packed_file);
		path[path_len] = '\0'; //0..path_len-1 + '\0'
		//get type
		uint8_t type;
		fread(&type, sizeof(uint8_t), 1, packed_file);
		//get size
		uint64_t size;
		fread(&size, sizeof(uint64_t), 1, packed_file);
		//get full_path
		char full_path[PATH_MAX];
		snprintf(full_path, sizeof(full_path), "%s/%s", output_dir, path);

		if (type == 0) //dir
			re_mkdir(full_path);

		else // exclude dir
		{
			/*
				strrchr_function:
					在参数 str 所指向的字符串中搜索最后一次出现字符 c（一个无符号字符）的位置。
				note:
					从字符串的末尾开始向前搜索，直到找到指定的字符或搜索完整个字符串。
					如果找到字符，它将返回一个指向该字符的指针，否则返回 NULL。
			*/
			// 创建父目录
			char *slash = strrchr(full_path, '/');
			if (slash)
			{
				*slash = '\0';
				re_mkdir(full_path);
				*slash = '/';
			}

			FILE *file = fopen(full_path, "wb");
			if (!file)
			{
				perror("fopen_ERROR ");
				continue;// goto the next time of while directly.
				// replace using the 'goto' sentence.
			}

			size_t remaining = size;
			char buffer[4096]; //缓冲带,[c不直接提供缓冲区，只能自己造轮子，这里简略用数组replace]。
			while (remaining > 0)
			{
				size_t to_read = auto_nmemb(sizeof(buffer), remaining) ;
				size_t bytes_read = fread(buffer, 1, to_read, packed_file);
				//END_case
				if (bytes_read == 0) //to_read == 0
					break; 	//end, up eff.
				//Formal_case
				fwrite(buffer, 1, bytes_read, file);
				remaining -= bytes_read;
			}

			fclose(file);
		}
	}
}

#endif
